#include <algorithm>
#include <functional>
#include <chrono>
#include "module_manager.hh"
#include "command_option.hh"
#include "service.hh"
#include "../corelib/logger/writer.hh"
#include "../corelib/util/os_util.hh"
#include "../corelib/util/string_util.hh"
#include "../corelib/object_pool.hpp"

using namespace kratos::corelib;

#define CHECK_RETURN_ERROR(c, r, log)\
    if (c) {\
        logecom("ModuleManager") << log << logend;\
        return r;\
    }

#define CHECK_RETURN_FATAL(c, r, log)\
    if (c) {\
        logecom("ModuleManager") << log << logend;\
        return r;\
    }

namespace kratos { namespace engine {

static std::string SUFFIX_PATCH(".patch"); // The suffix of patch file
static std::string SUFFIX_BACKUP(".backup"); // The suffix of backup file


ModuleManager::ModuleManager(Engine* engine)
    : engine_(engine), run_(false), pauseScanner_(false), scannerIntval_(SCANNER_INTERVAL) {
}

ModuleManager::~ModuleManager() {
    run_ = false;
    if (upgradeScanner_) {
        upgradeScanner_->join();
    }
    unloadAll();
    UpgradeRequest* request = nullptr;
    while (upgradeQueue_.try_dequeue(request)) {
        dispose(request);
    }
}

bool ModuleManager::loadModule(const std::string & serviceDir) {
    // Get all files in serviceDir
    StringVector fileNames;
    CHECK_RETURN_ERROR(!OS::getFiles(serviceDir, MODULE_SUFFIX, fileNames), true,
        "Plugins directory not found:" << serviceDir);
    bool eventModuleLoaded = false; // The flag for loaded module type of EVENT_MODULE
    for (auto& fileName : fileNames) {
        if (!isInWhiteList(fileName)) {
            // Not in white list
            continue;
        }
        Module module;
        // Check the module type
        CHECK_RETURN_FATAL(!module.load(fileName), false, "Load service module [" << fileName << "] failed");
        CHECK_RETURN_FATAL(!isKratosModule(module.getModuleType()), false, "Invalid module type [" << fileName
            << ", type [" << static_cast<std::underlying_type<ModuleType>::type>(module.getModuleType()) << "]")
        if (eventModuleLoaded && (module.getModuleType() == ModuleType::EVENT_MODULE)) {
            // ONLY one EVENT_MODULE can be loaded
            logecom("ModuleManager") << "There is more than one EVENT_MODULE loaded, module [" << fileName << "]" << logend;
            continue;
        }
        // Adjust module's core
        auto serviceName = getServiceName(fileName);
        module.setServiceName(serviceName);
        auto result = moduleMap_.emplace(serviceName, std::move(module));
        CHECK_RETURN_FATAL(!result.second, false, "Load service module [" << fileName << "] failed");
        auto moduleType = result.first->second.getModuleType();
        typicalModuleMap_[moduleType].push_back(&(result.first->second));
        if (moduleType == ModuleType::EVENT_MODULE) {
            eventModuleLoaded = true;
        } else if (moduleType == ModuleType::SERVICE_MODULE) {
            serviceModulePathList_.push_back(serviceDir);
        } else {
            // for others
        }
        logvcom("ModuleManager") << "Module " << fileName << " loaded" << logend;
        if (CommandOptionsRef.isDebug()) {
            // Load service module for debug mode
            if (!tryLoadServiceInDebug(fileName)) {
                return false;
            }
        }
        serviceRootPathMap_[serviceName] = serviceDir;
    }
    return true;
}

const std::string& ModuleManager::getServiceRootPath(const std::string& serviceName) {
    auto it = serviceRootPathMap_.find(serviceName);
    if (it == serviceRootPathMap_.end()) {
        static std::string NullPath;
        return NullPath;
    }
    return it->second;
} 

bool ModuleManager::unloadModule(const std::string & name) {
    auto it = moduleMap_.find(name);
    CHECK_RETURN_ERROR(it == moduleMap_.end(), false,
        "Try to unload module but not found, module name:" << name);
    auto moduleType = it->second.getModuleType();
    CHECK_RETURN_ERROR(!it->second.stop(), false,
        "Try to stop the module failed, module name:" << name);
    CHECK_RETURN_ERROR(!it->second.uninstall(), false,
        "Try to uninstall the module failed, module name:" << name);
    auto modulePtr = &it->second;
    CHECK_RETURN_ERROR(!it->second.unload(), false,
        "Try to unload the module failed, module name:" << name);
    auto& list = typicalModuleMap_[moduleType];
    list.erase(std::remove(list.begin(), list.end(), modulePtr));
    moduleMap_.erase(it);
    return true;
}

void ModuleManager::unloadAll() {
    for (auto& it : moduleMap_) {
        it.second.stop();
    }
    for (auto& it : moduleMap_) {
        it.second.uninstall();
        it.second.unload();
    }
    moduleMap_.clear();
}

bool ModuleManager::renamePatchFiles(const std::string& srcFilePath, const std::string& patchFilePath,
    const std::string& filePathBackup) {
    if (OS::isExists(filePathBackup)) {
        CHECK_RETURN_FATAL(!OS::removeFile(filePathBackup), false,
            "Remove previous backup failed, module:" << srcFilePath << ", System errno:" << info::syserrno
            << ", error:" << info::syserror);
    }
    CHECK_RETURN_FATAL(!OS::renameFile(srcFilePath, filePathBackup), false,
        "Cannot rename module name from:" << srcFilePath << " to:" << filePathBackup << ", System errno:" << info::syserrno << ", error:" << info::syserror);
    CHECK_RETURN_FATAL(!OS::renameFile(patchFilePath, srcFilePath), false,
        "Cannot rename module name from:" << patchFilePath << " to:" << srcFilePath << ", System errno:" << info::syserrno << ", error:" << info::syserror);
    return true;
}

void ModuleManager::startSanner() {
    upgradeScanner_.reset(new std::thread([&]() {
        constexpr static std::time_t MILLION_INTERVAL = 100;
        std::time_t tick = 0; // current tick
        std::time_t millions = scannerIntval_ * 1000; // interval
        std::chrono::milliseconds interval(MILLION_INTERVAL);
        while (run_) {
            if (pauseScanner_) {
                std::this_thread::sleep_for(interval);
                continue;
            }
            if (tick >= millions) {
                tick = 0;
            } else {
                std::this_thread::sleep_for(interval);
                tick += MILLION_INTERVAL;
                continue;
            }
            scanPatch();
        }
    }));
}

void ModuleManager::dealWithUpgradeRequest() {
    UpgradeRequest* request = nullptr;
    while (upgradeQueue_.try_dequeue(request)) {
        pauseScanner_ = true;
        try {
            upgradeModule(request->baseDir, request->name);
        } catch (std::exception& e) {
            logecom("ModuleManager") << "Catch exception when upgrading module:" << request->name
                << ", reason:" << e.what() << logend;
        }
        pauseScanner_ = false;
        dispose(request);
    }
}

void ModuleManager::tickModules(std::time_t current) {
    for (auto& it : moduleMap_) {
        it.second.getService()->onTick(current);
    }
}

void ModuleManager::scanPatch() {
    for (auto& dir : serviceModulePathList_) {
        StringVector fileNamePatch;
        if (!OS::getFiles(dir, SUFFIX_PATCH, fileNamePatch)) {
            break;
        }
        for (auto& moduleFile : fileNamePatch) {
            auto name = getServiceName(StringUtil::remove(moduleFile, SUFFIX_PATCH));
            auto request = allocate<UpgradeRequest>(dir, name);
            if (request) {
                upgradeQueue_.try_enqueue(request);
            } else {
                logecom("ModuleManager") << "Allocates UpgradeRequest failed" << logend;
            }
        }
    }
}

std::string ModuleManager::getLatestBackupName(const std::string& dir, const std::string & scrFileName) {
    StringVector fileNames;
    if (!OS::getFiles(dir, "", fileNames)) {
        return "";
    }
    std::size_t seq = 0;
    for (auto& moduleFile : fileNames) {
        if (StringUtil::hasSubstring(moduleFile, SUFFIX_BACKUP)) {
            std::string extension = OS::getExtension(moduleFile);
            auto count = std::stoul(extension);
            if (count > seq) {
                seq = count;
            }
        }
    }
    return scrFileName + SUFFIX_BACKUP + "." + std::to_string(seq + 1);
}

bool ModuleManager::upgradeModule(const std::string& baseDir, const std::string & name) {
    CHECK_RETURN_ERROR(moduleMap_.find(name) == moduleMap_.end(), false,
        "The module need to upgrade but not found, module name:" << name);
    auto patchFilePath = OS::completePath(baseDir, name + MODULE_SUFFIX + SUFFIX_PATCH);
    auto srcFilePath = OS::completePath(baseDir, name + MODULE_SUFFIX);
    auto filePathBackup = getLatestBackupName(baseDir, srcFilePath);
    if (!checkServiceModule(patchFilePath, name)) {
        return false;
    }
    // Upgrading flow
    // 1. Unload the old module
    // 2. Rename the old module backup
    // 3. Rename new module old module's name
    // 4. Load new module, rollback if any error occurs
    if (!unloadModule(name)) {
        return false;
    } else {
        // Module has been unloaded
        if (!renamePatchFiles(srcFilePath, patchFilePath, filePathBackup)) {
            rollback(srcFilePath, filePathBackup, name);
            return false;
        }
        Module module;
        if (!module.load(srcFilePath)) {
            logecom("ModuleManager") << "Loading patch module failed, patch name:" << srcFilePath << logend;
            rollback(srcFilePath, filePathBackup, name);
            return false;
        }
        if (!module.getService()->install(engine_)) {
            module.unload();
            logecom("ModuleManager") << "Install patch module failed, patch name:" << srcFilePath << logend;
            rollback(srcFilePath, filePathBackup, name);
            return false;
        }
        if (!module.getService()->start(engine_)) {
            module.unload();
            logecom("ModuleManager") << "Startup patch module failed, patch name:" << srcFilePath << logend;
            rollback(srcFilePath, filePathBackup, name);
            return false;
        }
        auto result = moduleMap_.emplace(name, std::move(module));
        CHECK_RETURN_FATAL(!result.second, false, "Module core failed");
        typicalModuleMap_[ModuleType::SERVICE_MODULE].push_back(&(result.first->second));
        logecom("ModuleManager") << "Upgrade module successfully, " << srcFilePath << " -> " << patchFilePath << logend;
        if (!OS::removeFile(filePathBackup)) {
            logecom("ModuleManager") << "Remove backup file failed, name:" << filePathBackup << ", System errno:"
                << info::syserrno << ", error:" << info::syserror << logend;
        }
    }
    return true;
}

Module* ModuleManager::getModule(const std::string & name) {
    auto it = moduleMap_.find(name);
    if (it == moduleMap_.end()) {
        return nullptr;
    }
    return &it->second;
}

ModuleMap& ModuleManager::getModules() {
    return moduleMap_;
}

const ModulePtrList & ModuleManager::getTypicalModules(ModuleType type) {
    return typicalModuleMap_[type];
}

void ModuleManager::onTick(std::time_t current) {
    if (!upgradeScanner_) {
        run_ = true;
        startSanner();
    }
    tickModules(current);
    dealWithUpgradeRequest();
}

void ModuleManager::addToWhite(const std::string & name) {
    whiteList_.push_back(name);
}

void ModuleManager::setUpgradingInterval(std::time_t intval) {
    scannerIntval_ = intval;
}

std::string ModuleManager::getServiceName(const std::string & path) {
    auto pos = path.find_last_of(DELIMETER);
    return std::string(path.begin() + pos + 1, path.end() - strlen(MODULE_SUFFIX));
}

std::string ModuleManager::getServicePathRoot(const std::string & path) {
    auto pos = path.find_last_of(DELIMETER);
    return std::string(path.begin(), path.begin() + pos);
}

bool ModuleManager::isInWhiteList(const std::string & fileName) {
    if (whiteList_.empty()) {
        return true;
    } else {
        auto moduleName = getServiceName(fileName);
        return (std::find(whiteList_.begin(), whiteList_.end(), moduleName) != whiteList_.end());
    }
}

bool ModuleManager::tryLoadServiceInDebug(const std::string & path) {
    auto moduleName = getServiceName(path);
    auto folderName = moduleName + ".service";
    auto pathRoot = getServicePathRoot(path) + DELIMETER + folderName;
    StringVector fileNames;
    CHECK_RETURN_FATAL(!OS::getFiles(pathRoot, MODULE_SUFFIX, fileNames), true,
        "Service directory not found:" << pathRoot);
    for (auto& fileName : fileNames) {
        Module module;
        CHECK_RETURN_FATAL(!module.load(fileName), false, "Load service module [" << fileName << "] failed");
        auto moduleType = module.getModuleType();
        CHECK_RETURN_FATAL(moduleType != ModuleType::SERVICE_MODULE, false,
            "ONLY SERVICE_MODULE cannot load as service, module [" << fileName << "]")
        auto serviceName = getServiceName(fileName);
        auto result = moduleMap_.emplace(serviceName, std::move(module));
        CHECK_RETURN_FATAL(!result.second, false, "Load service module [" << fileName << "] failed");
        typicalModuleMap_[moduleType].push_back(&(result.first->second));
        logvcom("ModuleManager") << "Service module " << fileName << " loaded" << logend;
    }
    if (!fileNames.empty()) {
        serviceModulePathList_.push_back(pathRoot);
    }
    return true;
}

bool ModuleManager::rollback(const std::string& srcFilePath, const std::string& filePathBackup, const std::string & name) {
    logwcom("ModuleManager") << "Rollback starting, module name:" << name
        << ", module:" << srcFilePath << logend;
    if (OS::isExists(filePathBackup)) {
        CHECK_RETURN_FATAL(!OS::removeFile(srcFilePath), false, "Remove file " << srcFilePath << " failed");
        CHECK_RETURN_FATAL(!OS::renameFile(filePathBackup, srcFilePath), false, "Rename from "
            << filePathBackup << " to " << srcFilePath << " failed");
    }
    Module module;
    if (module.load(srcFilePath)) {
        if (module.getService()->install(engine_)) {
            if (module.getService()->start(engine_)) {
                auto moduleType = module.getModuleType();
                auto result = moduleMap_.emplace(name, std::move(module));
                CHECK_RETURN_FATAL(!result.second, false, "Module core failed");
                typicalModuleMap_[moduleType].push_back(&(result.first->second));
                logwcom("ModuleManager") << "Rollback finished, module name:"
                    << name << ", module:" << srcFilePath << logend;
                return true;
            } else {
                logfcom("ModuleManager") << "Rollback failed for module:" << srcFilePath
                    << ", !module.getService()->start()" << logend;
            }
        } else {
            logfcom("ModuleManager") << "Rollback failed for module:" << srcFilePath
                << ", !module.getService()->install()" << logend;
        }
    } else {
        logfcom("ModuleManager") << "Rollback failed for module:" << srcFilePath
            << ", !module.load()" << logend;
    }
    return false;
}

bool ModuleManager::checkServiceModule(const std::string & filePath, const std::string& name) {
    Module module;
    CHECK_RETURN_FATAL(!module.load(filePath), false, "Loading patch module failed, patch name:" << filePath);
    auto moduleType = module.getModuleType();
    CHECK_RETURN_FATAL(moduleType != ModuleType::SERVICE_MODULE, false,
        "ONLY service module can be upgraded, module name:" << name);
    module.clearService();
    module.forceUnload();
    return true;
}

}}
